/**
 * @file lv_sdfs.c
 * Implementation of sd file system
 * The API is compatible with the lv_fs module.
 */
/*********************
 *      INCLUDES
 *********************/
#include "lv_sdfs.h"

#if LV_USE_FILESYSTEM && USE_LV_SD_FS
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "lv_fs.h"
#include "../../board.h"

/*********************
 *      DEFINES
 *********************/
/* File attribute bits for directory entry (FILINFO.fattrib) (for intern used */
#define INTERN_FILE_HID 0x02    /* Hidden */
#define INTERN_FILE_SYS 0x04    /* System */
#define INTERN_FILE_DIR 0x10    /* Directory */
#define INTERN_FILE_ARC 0x20    /* Archive */

//fs_api.h
#define ERR_FS_INVALID_PARAMETER                       -4200019
#define ERR_FS_NO_MORE_MEMORY                          -4200025
#define ERR_FS_DISK_FULL                               -4200030
#define ERR_FS_FILE_NOT_EXIST                          -4200062

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
/**********************
 *  STATIC VARIABLES
 **********************/
static int sdfs_inited = 0;

/**************************************************************************************************************
 *      MACROS
 **************************************************************************************************************/

/**************************************************************************************************************
 *   STATIC FUNCTIONS
 **************************************************************************************************************/
/**
 * Get fs error result
 * @param errcode error code
 * @return fs error result
 */
static lv_fs_res_t lv_sdfs_get_err_res(int32_t errcode)
{
    lv_fs_res_t res;

    switch(errcode) {
    case ERR_FS_INVALID_PARAMETER:
        res = LV_FS_RES_INV_PARAM;
        break;

    case ERR_FS_NO_MORE_MEMORY:
        res = LV_FS_RES_OUT_OF_MEM;
        break;

    case ERR_FS_DISK_FULL:
        res = LV_FS_RES_FULL;
        break;

    case ERR_FS_FILE_NOT_EXIST:
        res = LV_FS_RES_NOT_EX;
        break;

    default:
        res = LV_FS_RES_UNKNOWN;
        break;
    }

    return res;
}

/**
 * Give the state of the sdfs
 * @return true if sdfs is initialized and can be used else false
 */
static bool lv_sdfs_ready(struct _lv_fs_drv_t * drv)
{
    (void)drv;
    return sdfs_inited == 1;
}

/**
 * Open a file in sdfs
 * @param file_p pointer to a lv_sdfs_file_t variable
 * @param fname name of the file.
 * @param mode element of 'fs_mode_t' enum or its 'OR' connection (e.g.
 * FS_MODE_WR | FS_MODE_RD)
 * @return LV_FS_RES_OK: no error, the file is opened
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_open(struct _lv_fs_drv_t * drv, void *file_p, const char *fname, lv_fs_mode_t mode)
{
    if((FS_TFCardExist() != TRUE) || (FS_TFCardAvailable() != TRUE))
    {
        printf("lv_sdfs_open failed with return result is LV_FS_RES_HW_ERR\n");
        return LV_FS_RES_HW_ERR;
    }
    uint32_t flags = 0;
    int32_t *fd = (int32_t *)file_p;
    char *full_path = NULL;

    (void)drv;

    if (mode == LV_FS_MODE_WR) flags = FS_O_WRONLY|FS_O_CREAT; //"wbc";
    else if (mode == LV_FS_MODE_RD) flags = FS_O_RDONLY; //"rb";
    else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = FS_O_RDWR|FS_O_CREAT; //"wbc";
    else if (mode == (LV_FS_MODE_RD | LV_FS_MODE_APPEND)) flags = FS_O_RDWR|FS_O_APPEND;//"rb+";
    else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_APPEND)) flags = FS_O_RDWR|FS_O_APPEND;//"ab+";
    else flags = FS_O_RDWR|FS_O_CREAT;///"wb+";

    full_path = (char *)lv_mem_alloc(PATH_MAX);
    if (full_path == NULL) {
        printf("open Mem not enough %s\n", fname);
        return LV_FS_RES_OUT_OF_MEM;
    }

    memset(full_path, 0, PATH_MAX);
    strcpy(full_path, "D:\\");
    strcat(full_path, fname);

    *fd = FS_Open((PCSTR)full_path, flags, 0);
    lv_mem_free(full_path);
    full_path = NULL;
    if (*fd <= 0) {
        printf("*fd is %d when FS_Open is fail in %s!\n",*fd,__FUNCTION__);
        return lv_sdfs_get_err_res(*fd);
    }

    return LV_FS_RES_OK;
}

/**
 * Close an opened file
 * @param file_p pointer to an 'sdfs_file_t' variable. (opened with lv_sdfs_open)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_close(struct _lv_fs_drv_t * drv, void *file_p)
{
    int32_t fd = *(int32_t *)file_p;
    int32_t ret = 0;

    (void)drv;

    if (fd <= 0) {
        return LV_FS_RES_OK;
    }

    ret = FS_Close(fd);
    if (ret < 0) {
        printf("ret is %d when FS_Close is fail in %s!\n",ret,__FUNCTION__);
        return lv_sdfs_get_err_res(ret);
    }

    return LV_FS_RES_OK;
}

/**
 * Remove a file. The file can not be opened.
 * @param fname '\0' terminated string
 * @return LV_FS_RES_OK: no error, the file is removed
 *         LV_FS_RES_DENIED: the file was opened, remove failed
 */
static lv_fs_res_t lv_sdfs_remove(struct _lv_fs_drv_t * drv, const char *fname)
{
    int32_t ret = 0;
    char *full_path = NULL;

    (void)drv;
    full_path = (char *)lv_mem_alloc(PATH_MAX);
    if (full_path == NULL) {
        printf("open Mem not enough %s\n", fname);
        return LV_FS_RES_OUT_OF_MEM;
    }
    memset(full_path, 0, PATH_MAX);
    strcpy(full_path, "D:\\");
    strcat(full_path, fname);

    ret= FS_Delete((PCSTR)full_path);
    lv_mem_free(full_path);
    if (ret < 0) {
        printf("remove fail %d", ret);
        return lv_sdfs_get_err_res(ret);
    }

    return LV_FS_RES_OK;
}

/**
 * Read data from an opened file
 * @param file_p pointer to an 'sdfs_file_t' variable. (opened with lv_sdfs_open )
 * @param buf pointer to a memory block where to store the read data
 * @param btr number of Bytes To Read
 * @param br the real number of read bytes (Byte Read)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_read(struct _lv_fs_drv_t * drv, void *file_p, void *buf, uint32_t btr, uint32_t *br)
{
    int32_t fd = *(int32_t *)file_p;
    int32_t ret = 0;

    (void)drv;

    *br = 0;
    if (fd <= 0) {
        printf("fd is %d in %s!\n",fd,__FUNCTION__);
        return LV_FS_RES_INV_PARAM;
    }

    ret= FS_Read(fd, buf, btr);
    *br = ret;
    if (ret != btr) {
        printf("ret is %d and btr is %d when FS_Read is fail in %s!\n",ret,btr,__FUNCTION__);
        return lv_sdfs_get_err_res(ret);
    }

    return LV_FS_RES_OK;
}

/**
 * Write data to an opened file
 * @param file_p pointer to an int variable. (opened with lv_sdfs_open)
 * @param buf pointer to a memory block which content will be written
 * @param btw the number Bytes To Write
 * @param bw The real number of written bytes (Byte Written)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_write(struct _lv_fs_drv_t * drv, void *file_p, const void *buf, uint32_t btw, uint32_t *bw)
{
    int32_t fd = *(int32_t *)file_p;
    int32_t ret = 0;

    (void)drv;

    *bw = 0;
    if (fd <= 0) {
        printf("fd is %d in %s!\n",fd,__FUNCTION__);
        return LV_FS_RES_INV_PARAM;
    }

    ret = FS_Write(fd, (unsigned char *)buf, btw);
    *bw = ret;
    if (ret != btw) {
        *bw = 0;
        printf("ret is %d and btw is %d when FS_Write is fail in %s!\n",ret,btw,__FUNCTION__);
        return lv_sdfs_get_err_res(ret);
    }

    return LV_FS_RES_OK;
}

/**
 * Set the read write pointer. Also expand the file size if necessary.
 * @param file_p pointer to an int variable. (opened with lv_sdfs_open )
 * @param pos the new position of read write pointer
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_seek(struct _lv_fs_drv_t * drv, void *file_p, uint32_t pos)
{
    int32_t fd = *(int32_t *)file_p;
    int32_t ret = 0;

    (void)drv;

    if (fd <= 0) {
        printf("fd is %d in %s!\n",fd,__FUNCTION__);
        return LV_FS_RES_INV_PARAM;
    }

    ret = FS_Seek(fd, pos, SEEK_SET);
    if (ret < 0) {
        printf("ret is %d when FS_Seek is fail in %s!\n",ret,__FUNCTION__);
        return lv_sdfs_get_err_res(ret);
    }

    return LV_FS_RES_OK;
}

/**
 * Give the size of the file in bytes
 * @param file_p file_p pointer to an int variable. (opened with
 * lv_sdfs_open )
 * @param size_p pointer to store the size
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_size(struct _lv_fs_drv_t * drv, void *file_p, uint32_t *size_p)
{
    int32_t fd = *(int32_t *)file_p;

    (void)drv;

    if (fd <= 0) {
        return LV_FS_RES_INV_PARAM;
    }

    *size_p = FS_GetFileSize(fd);

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_sdfs_rename(struct _lv_fs_drv_t * drv, const char * oldname, const char * newname)
{
    int32_t ret = 0;

    (void)drv;

    ret = FS_Rename((PCSTR)oldname, (PCSTR)newname);
    if (ret < 0)
        return lv_sdfs_get_err_res(ret);

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_sdfs_free(struct _lv_fs_drv_t * drv, uint32_t * total_p, uint32_t * free_p)
{
    FS_INFO info;

    (void)drv;

    FS_GetFSInfo("D", &info);

    *total_p = (info.iToTalSize/1000);
    *free_p = (info.iToTalSize - info.iUsedSize)/1000;
    return LV_FS_RES_OK;
}

/**
 * Get dir handle
 * @param in: rddir_prddir_p file_p pointer to an int variable.
 * @param in: path dir path (e.g. "D:\\*", "D:\\Music\\*")
 * @return out: LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_dir_open (struct _lv_fs_drv_t * drv, void * rddir_p, const char * path)
{
    if((FS_TFCardExist() != TRUE) || (FS_TFCardAvailable() != TRUE))
    {
        printf("lv_sdfs_dir_open failed with return result is LV_FS_RES_HW_ERR\n");
        return LV_FS_RES_HW_ERR;
    }
    int32_t *pfd = (int32_t *)rddir_p;
    char *opdir_name = NULL;
    uint8_t len = 0;

    (void)drv;

    opdir_name = (char *)lv_mem_alloc(PATH_MAX);
    if (opdir_name == NULL) {
        printf("opendir Mem not enough %s\n", path);
        return LV_FS_RES_OUT_OF_MEM;
    }
    memset(opdir_name, 0, PATH_MAX);
    strcpy(opdir_name, "D:\\");
    strcat(opdir_name, path);

    len = strlen(opdir_name);
    if(opdir_name[len-1] == '*') {
        //because FS_Open_DIR will add '/*' in the end, here need remove
        opdir_name[len-2] = '\0';
    }
    *pfd = FS_Open_DIR((PCSTR)opdir_name);
    lv_mem_free(opdir_name);
    opdir_name = NULL;
    printf("*pfd is %x in %s!\n",*pfd,__FUNCTION__);
    if (*pfd == 0) {
        printf("opendir Fail %s\n", path);
        return LV_FS_RES_UNKNOWN;
    }

    return LV_FS_RES_OK;
}

/**
 * Get dir/file name and attribute
 * @param in: file_p file_p pointer to an int variable. (opened with lv_sdfs_dir_open )
 * @param out: dir or file name (not including path)
 * @return out: Low 4 bit : LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 *         High 4 bit : see blow
 *             FILE_DIR	   0x10	   directory
 *             FILE_ARC	   0x20	   normal file
 *             FILE_HID    0x40    hide(or system) dir(or file)
 */
static lv_fs_res_t lv_sdfs_dir_read (struct _lv_fs_drv_t * drv, void * rddir_p, char * fn)
{
    DIRENT_INFO  dirinfo;
    int32_t fd = *(int32_t *)rddir_p;
    lv_fs_res_t  res = LV_FS_RES_OK;
    int fat_ret = 0;

    (void)drv;

    if (fd == 0) {
        return LV_FS_RES_INV_PARAM;
    }

    memset(&dirinfo, 0, sizeof(DIRENT_INFO));
    fat_ret = FS_Get_Next_File(fd, &dirinfo);
    if (fat_ret == 0) {
        printf("lv_sdfs rd failed\n");
        return LV_FS_RES_UNKNOWN;
    }
    strcpy(fn, dirinfo.d_name);
    if (dirinfo.d_type == FS_FILE_DIR)
        res |= FILE_DIR;
    else if (dirinfo.d_type == FS_FILE_REG)
        res |= FILE_ARC;
    //printf("lv_sdfs rd res = %d, fname = %s.\n", res, fn);

    return res;
}

/**
 * close dir handle
 * @param in: file_p file_p pointer to an int variable. (opened with lv_sdfs_dir_open )
 * @return out: LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t lv_sdfs_dir_close (struct _lv_fs_drv_t * drv, void * rddir_p)
{
    int32_t fd = *(int32_t *)rddir_p;
    bool ret = 0;

    (void)drv;

    if (fd == 0) {
        return LV_FS_RES_OK;
    }

    ret = FS_Close_DIR(fd);
    if (ret) {
        return LV_FS_RES_UNKNOWN;
    }

    return LV_FS_RES_OK;
}

/**************************************************************************************************************
 *   GLOBAL FUNCTIONS
 **************************************************************************************************************/
/**
 * Create a driver for sdfs and initialize it.
 */
void lv_sdfs_init(void)
{
    lv_fs_drv_t sdfs_drv;

    if (sdfs_inited) {
        printf("sd fs has been inited\n");
        return;
    }

    memset(&sdfs_drv, 0, sizeof(lv_fs_drv_t));

    sdfs_drv.letter     = SD_FS_LETTER;
    sdfs_drv.file_size  = sizeof(uint32_t);
    sdfs_drv.rddir_size  = sizeof(uint32_t);
    sdfs_drv.ready_cb      = lv_sdfs_ready;

    sdfs_drv.open_cb   = lv_sdfs_open;
    sdfs_drv.close_cb  = lv_sdfs_close;
    sdfs_drv.remove_cb = lv_sdfs_remove;
    sdfs_drv.read_cb   = lv_sdfs_read;
    sdfs_drv.write_cb  = lv_sdfs_write;
    sdfs_drv.seek_cb   = lv_sdfs_seek;
    sdfs_drv.size_cb   = lv_sdfs_size;
    sdfs_drv.rename_cb   = lv_sdfs_rename;
    sdfs_drv.free_space_cb   = lv_sdfs_free;
    sdfs_drv.dir_open_cb = lv_sdfs_dir_open;
    sdfs_drv.dir_read_cb = lv_sdfs_dir_read;
    sdfs_drv.dir_close_cb = lv_sdfs_dir_close;

    lv_fs_drv_register(&sdfs_drv);

    sdfs_inited = 1;
}

#endif /* USE_LV_FILESYSTEM && USE_LV_SD_FS */

